const { serialize } = require("./helpers");

const assert = require("assert");

Feature("Test Image object");

const config = `
  <View>
    <Image name="img" value="$image"></Image>
    <RectangleLabels name="tag" toName="img">
      <Label value="Planet"></Label>
      <Label value="Moonwalker" background="blue"></Label>
    </RectangleLabels>
  </View>`;

const configEllipse = `
  <View>
    <Image name="img" value="$image"></Image>
    <EllipseLabels name="tag" toName="img">
      <Label value="Planet"></Label>
      <Label value="Moonwalker" background="blue"></Label>
    </EllipseLabels>
  </View>`;

const perRegionConfig = `
  <View>
    <Image name="img" value="$image"></Image>
    <RectangleLabels name="tag" toName="img">
      <Label value="Planet"></Label>
      <Label value="Moonwalker" background="blue"></Label>
    </RectangleLabels>
    <TextArea name="answer" toName="img" perRegion="true" />
  </View>`;

const createRegion = (from_name, type, values) => ({
  id: "Dx_aB91ISN",
  source: "$image",
  from_name,
  to_name: "img",
  type,
  origin: "manual",
  value: {
    height: 10.458911419423693,
    rotation: 0,
    width: 12.4,
    x: 50.8,
    y: 5.869797225186766,
    ...values,
  },
});

const annotationMoonwalker = {
  id: "1001",
  lead_time: 15.053,
  result: [createRegion("tag", "rectanglelabels", { rectanglelabels: ["Moonwalker"] })],
};

// perregion regions have the same id as main region
// and their own data (`text` in this case)
const annotationWithPerRegion = {
  id: "1002",
  result: [annotationMoonwalker.result[0], createRegion("answer", "textarea", { text: ["blah"] })],
};

const image =
  "https://htx-pub.s3.us-east-1.amazonaws.com/examples/images/nick-owuor-astro-nic-visuals-wDifg5xc9Z4-unsplash.jpg";

Scenario("Check Rect region for Image", async ({ I, LabelStudio, AtImageView, AtOutliner, AtPanels }) => {
  const params = {
    config,
    data: { image },
    annotations: [annotationMoonwalker],
  };
  const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

  I.amOnPage("/");
  LabelStudio.init(params);
  AtDetailsPanel.collapsePanel();

  LabelStudio.waitForObjectsReady();
  await AtImageView.lookForStage();
  AtOutliner.seeRegions(1);
  // select first and only region
  AtOutliner.clickRegion(1);
  AtOutliner.seeSelectedRegion();

  // click on region's rect on the canvas
  AtImageView.clickAt(330, 80);
  I.waitTicks(1);
  AtOutliner.dontSeeSelectedRegion();
});

Scenario("Image with perRegion tags", async ({ I, LabelStudio, AtOutliner }) => {
  let result;
  const params = {
    config: perRegionConfig,
    data: { image },
    annotations: [annotationWithPerRegion],
  };

  I.amOnPage("/");
  LabelStudio.init(params);

  LabelStudio.waitForObjectsReady();
  AtOutliner.seeRegions(1);
  // select first and only region
  AtOutliner.clickRegion(1);
  AtOutliner.seeSelectedRegion();

  // check that there is deserialized text for this region; and without doubles
  I.seeNumberOfElements(locate("mark").withText("blah"), 1);

  // add another note via textarea
  I.fillField("[name=answer]", "another");
  I.pressKey("Enter");
  // texts are concatenated in the regions list (now with \n, so check separately)
  I.seeNumberOfElements(locate("mark").withText("blah"), 1);
  I.seeNumberOfElements(locate("mark").withText("another"), 1);
  // and there is only one tag with all these texts
  I.seeNumberOfElements("mark", 2);

  // serialize with two textarea regions
  result = await I.executeScript(serialize);
  assert.strictEqual(result.length, 2);
  assert.strictEqual(result[0].id, "Dx_aB91ISN");
  assert.strictEqual(result[1].id, "Dx_aB91ISN");
  assert.deepStrictEqual(result[0].value.rectanglelabels, ["Moonwalker"]);
  assert.deepStrictEqual(result[1].value.text, ["blah", "another"]);

  // delete first deserialized text and check that only "another" left
  I.click(locate('[aria-label="Delete Region"]').inside('[data-testid="textarea-region"]'));
  I.dontSeeElement(locate("mark").withText("blah"));
  I.seeElement(locate("mark").withText("another"));

  result = await I.executeScript(serialize);
  assert.strictEqual(result.length, 2);
  assert.deepStrictEqual(result[0].value.rectanglelabels, ["Moonwalker"]);
  assert.deepStrictEqual(result[1].value.text, ["another"]);

  // delete also "another" region
  I.click(locate('[aria-label="Delete Region"]').inside('[data-testid="textarea-region"]'));
  // there are should be no texts left at all
  I.dontSeeElement(locate("mark"));

  result = await I.executeScript(serialize);
  assert.strictEqual(result.length, 1);
  assert.deepStrictEqual(result[0].value.rectanglelabels, ["Moonwalker"]);
});

const outOfBoundsFFs = new DataTable(["FF_DEV_3793"]);

outOfBoundsFFs.add([true]);
outOfBoundsFFs.add([false]);

Data(outOfBoundsFFs).Scenario(
  "Can't create rectangles outside of canvas",
  async ({ I, AtLabels, AtOutliner, AtImageView, LabelStudio, AtPanels, current }) => {
    LabelStudio.setFeatureFlags({
      fflag_fix_front_dev_3793_relative_coords_short: current.FF_DEV_3793,
    });
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");

    LabelStudio.init({
      config,
      data: { image },
      task: {
        id: 0,
        annotations: [{ id: 1001, result: [] }],
        predictions: [],
      },
    });
    AtDetailsPanel.collapsePanel();

    LabelStudio.waitForObjectsReady();
    await AtImageView.lookForStage();

    const stage = AtImageView.stageBBox();

    I.say("Drawing region in the upper left corner");
    AtLabels.clickLabel("Planet");
    AtImageView.drawByDrag(100, 100, -200, -200);

    I.say("Drawing region in the upper right corner");
    AtLabels.clickLabel("Planet");
    AtImageView.drawByDrag(stage.width - 100, 100, stage.width + 100, -100);

    I.say("Drawing region in the bottom left corner");
    AtLabels.clickLabel("Planet");
    AtImageView.drawByDrag(100, stage.height - 100, -100, stage.height + 100);

    I.say("Drawing region in the bottom right corner");
    AtLabels.clickLabel("Planet");
    AtImageView.drawByDrag(stage.width - 100, stage.height - 100, stage.width + 100, stage.height + 100);

    AtOutliner.seeRegions(4);

    const result = await LabelStudio.serialize();

    const [r1, r2, r3, r4] = result.map((r) => r.value);

    I.say("First region should be in the corner");
    assert.strictEqual(r1.x, 0);
    assert.equal(r1.y, 0);

    I.say("Second region should be in the corner");
    assert.equal(Math.round(r2.x + r2.width), 100);
    assert.equal(r2.y, 0);

    I.say("Third region should be in the corner");
    assert.equal(r3.x, 0);
    assert.equal(Math.round(r3.y + r3.height), 100);

    I.say("Fourth region should be in the corner");
    assert.equal(Math.round(r4.x + r4.width), 100);
    assert.equal(Math.round(r4.y + r4.height), 100);
  },
);

Data(outOfBoundsFFs).Scenario(
  "Can't create ellipses outside of canvas",
  async ({ I, AtLabels, AtOutliner, AtImageView, LabelStudio, AtPanels, current }) => {
    LabelStudio.setFeatureFlags({
      fflag_fix_front_dev_3793_relative_coords_short: current.FF_DEV_3793,
    });
    const AtDetailsPanel = AtPanels.usePanel(AtPanels.PANEL.DETAILS);

    I.amOnPage("/");

    LabelStudio.init({
      config: configEllipse,
      data: { image },
      task: {
        id: 0,
        annotations: [{ id: 1001, result: [] }],
        predictions: [],
      },
    });
    AtDetailsPanel.collapsePanel();

    LabelStudio.waitForObjectsReady();
    await AtImageView.lookForStage();

    const stage = AtImageView.stageBBox();
    const ellipses = [
      // top-left corner
      [100, 100, -200, -200],
      // top-right corner
      [stage.width - 100, 100, stage.width + 100, -100],
      // bottom-left corner
      [100, stage.height - 100, -100, stage.height + 100],
      // bottom-right corner
      [stage.width - 100, stage.height - 100, stage.width + 100, stage.height + 100],
    ];

    for (const ellipse of ellipses) {
      I.say("Drawing region in the upper left corner");
      AtLabels.clickLabel("Planet");
      AtImageView.drawByDrag(...ellipse);
    }

    AtOutliner.seeRegions(4);

    const result = await LabelStudio.serialize();
    const radiusX = (100 / stage.width) * 100;
    const radiusY = (100 / stage.height) * 100;

    for (let i = 0; i < result.length; i++) {
      const res = result[i].value;
      const region = ellipses[i];

      I.say("Make sure ellipse radius is correct (should be same for all)");
      // toFixed is to bypass JS floating point precision limitations (f32 sucks)
      assert.strictEqual(res.radiusX.toFixed(3), radiusX.toFixed(3));
      assert.strictEqual(res.radiusY.toFixed(3), radiusY.toFixed(3));

      I.say("Make sure that center is in correct spot");
      const [expectedX, expectedY] = [(region[0] / stage.width) * 100, (region[1] / stage.height) * 100];

      assert.strictEqual(res.x.toFixed(3), expectedX.toFixed(3));
      assert.strictEqual(res.y.toFixed(3), expectedY.toFixed(3));
    }
  },
);
